package core.semantic;

import java.util.Stack;

import core.KeyWord;
import core.LexicalToken;
import core.Token;
import core.lexical.LexicalAnalyser;
import core.semantic.SemanticTuple.Class;

/**
 * It represents the semantic analyser of the FORD language compiler.
 */
public class SemanticAnalyser {

	private Stack<KeyWord> stack;
	private SymbolTable table;
	private LexicalAnalyser scanner;
	private SemanticException semanticException;
	
	/**
	 * Creates a new SemanticAnalyser.
	 */
	public SemanticAnalyser(LexicalAnalyser scanner) {
		stack = new Stack<KeyWord>();
		table = new SymbolTable();
		this.scanner = scanner;
		semanticException = new SemanticException();
	}
	
	
	/**
	 * It finds the tuple that represents the identifier provided.
	 * @param identifier The identifier of the tuple.
	 * @param c The class of the identifier.
	 * @return The semantic tuple that represents the identifier provided.
	 */
	private SemanticTuple find(String identifier, Class c){
		return table.find(identifier, c);
	}
	
	/**
	 * It pushes the provided <code>KeyWord</code> at the stack.
	 * @param k The <code>KeyWord</code> to be pushed.
	 * @return The same <code>KeyWord</code> that had been pushed.
	 */
	public KeyWord push(KeyWord k){
		System.out.println("Push action executed: ");
		System.out.println("Before: " + stack);
		KeyWord keyWordPushed = stack.push(k);
		System.out.println("After: " + stack + ", symbol: " + keyWordPushed + " pushed.");
		System.out.println();
		return keyWordPushed;
	}
	
	/**
	 * It pops the <code>KeyWord</code> at the top of the stack.
	 * @return The <code>KeyWord</code> that has been popped.
	 */
	public KeyWord pop(){
		System.out.println("Pop action executed: ");
		System.out.println("Before: " + stack);
		KeyWord keyWordPopped = stack.pop();
		System.out.println("After: " + stack + ", symbol: " + keyWordPopped + " popped.");
		System.out.println();
		return keyWordPopped;
	}
	
	/**
	 * It adds a semantic tuple to the Symbol Table.
	 * @param classType
	 * @param token
	 * @param word
	 * @since 21/03/2008
	 */
	public void addSemanticTuple(Class classType, LexicalToken token, KeyWord word) {
		System.out.println("New identifier added to the SimbolTable: ");
		System.out.println("Identifier: " + token.getToken() +" Type: " + word.getToken());
		System.out.println();

		boolean added = table.add(new SemanticTuple(token.getToken(), 
				word, 
				classType));
		
		if(!added){
			semanticException.addError(scanner.getLineNumber(), "Duplicated identifier " + token.getToken());
		}
	}

	/**
	 * Its pushes the method type.
	 * @since 21/03/2008
 	 */
	public void pushMethodType(KeyWord word) {
		push(word);
	}
	

	/**
	 * Verify the compatibility type in the relational expression.
	 * @since 17/03/2008
	 */
	public void verifyRelationalTypeCompatibility() {
		System.out.println("****** Verifying relational type compatibility******");
		KeyWord t1 = pop();
		KeyWord t2 = pop();
		System.out.println("****************************************************");

		if(!verifyTypeCompatibility(t1, t2)){
			semanticException.addError(scanner.getLineNumber(), "The relational operations are undifined for the argument type(s) "+ t1.getToken() + ", " + t2.getToken());
		}

	}
	
	
	/**
	 * Verify the compatibility type in the arithmetic expression.
	 * @since 17/03/2008
	 */
	public void verifyArithmeticTypeCompatibility() {
		System.out.println("****** Verifying arithmetic type compatibility******");
		KeyWord t1 = pop();
		KeyWord t2 = pop();
		
		if(!verifyTypeCompatibility(t1, t2)){
			semanticException.addError(scanner.getLineNumber(), "Type mismatch: cannot convert from "+ t1.getToken() + " to " + t2.getToken());			
		}
		
		if(t1.equals(KeyWord.VOID) || t2.equals(KeyWord.VOID)){
			push(KeyWord.INT);
		}else{
			push(t1);
		}
		System.out.println("****************************************************");
	}
	
	/**
	 * Verify the compatibility type in of the two symbols in the top of the stack.
	 * @since 17/03/2008
	 */
	private boolean verifyTypeCompatibility(KeyWord t1, KeyWord t2) {
	
		//coringa
		if(t1.equals(KeyWord.VOID) || t2.equals(KeyWord.VOID)){
			return true;
		}
		
		if(!t1.equals(t2)){
			return false;
		}
		return true;
	}
	
	public void verifyAtributionCompatibility() {
		KeyWord t1 = pop();
		KeyWord t2 = pop();
		
		if(!verifyTypeCompatibility(t1, t2)){			
			semanticException.addError(scanner.getLineNumber(), "You can't atribute " + t1 + " to " + t2);
		}		
	}
	
	/**
	 * Verify if the semantic tuple has been declared.
	 * @param buffer
	 * @param c The semantic tuple to be verified.
	 * @return The token corresponding to the semantic tuple.
	 * @since 17/03/2008
	 */
	private Token verifyDeclaration(SemanticTuple.Class c, Stack<LexicalToken> buffer){
		Token id = buffer.peek();
		LexicalToken t2 = null;
		LexicalToken t3 = null;
		LexicalToken t4 = null;

		//verify if it is an array
		if(id.getToken().equals("]")){
			t2 = buffer.pop();
			t3 = buffer.pop();
			t4 = buffer.pop();
			id = buffer.peek();
		}

		if(find(id.getToken(), c) == null){
			System.out.println("error");
			semanticException.addError(scanner.getLineNumber(), "Identifier "+ id.getToken() + " never declared");
		}

		if(t2 != null){
			buffer.push(t4);
			buffer.push(t3);
			buffer.push(t2);
		}

		return id;
	}

	public SemanticException getSemanticException() {
		return semanticException;
	}

	public void pushIdentifierType(Class variable, Stack<LexicalToken> buffer) {
		verifyDeclaration(variable, buffer);
		SemanticTuple st = find(buffer.peek().getToken(), variable);
		if(st == null){
			push(KeyWord.VOID);
			return;
		}

		push(st.getType());
	}

}
